package org.oos.services;

import static org.oos.services.Constants.LOG_TAG;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import org.apache.commons.codec.digest.DigestUtils;
import org.oos.dao.SearchServiceDao;
import org.oos.dao.UserServiceDao;
import org.oos.data.SearchService;
import org.oos.data.UserService;
import org.oos.services.log.Logger;
import org.oos.services.log.StdLogger;
import org.oos.services.util.AtomUtils;
import org.oos.services.util.URLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXParseException;

/**
 * This is the main class of the library. All interaction with the 11870 api is going to be handled from this class.
 * It must be instantiated passing as parameters the 11870 api key and secret. 
 */
public class OOS4Java
{
	private final String API_BASE_URL = "http://apachex.11870.org/api/v1";
	public final String APP_TOKEN;
	public final String SECRET;
	private final String EMAIL;
	private final String AUTH_TOKEN;
	private final String USER_SLUG;

	public AtomUtils atom;
	public URLUtils urlUtils;
	
	private Logger log;
	public void setLogger(Logger log) {
		this.log = log;
	}
	
	public final APPDataSource datasource;
	
	/**
	 * Returns true if the instance has been constructed for authenticated use. 
	 */
	public boolean authenticated()
	{
		return !(EMAIL==null || AUTH_TOKEN==null || USER_SLUG==null || EMAIL.length()==0 || AUTH_TOKEN.length()==0 || USER_SLUG.length()==0);
	}
	
	/**
	 * Common stuff for initialization.
	 */
	private void init()
	{
		atom = new AtomUtils();
		urlUtils = new URLUtils();
		log = new StdLogger();
	}
	
	/**
	 * This constructor will be used when the OOS4Java is going to be used in non authenticated mode, hence
	 * having no access to user's personal agenda. This limits the API use to searching.
	 * 
	 * The class must be constructed passing the application token and 
	 * secret, which must have been got from 11870.com. See http://groups.google.com/group/api-11870/web/api-keys
	 * for further information.
	 * 
	 * @param app_token application token provided by 11870.com
	 * @param secret application secret provided by 11870.com
	 */
	public OOS4Java(String app_token, String secret)
	{
		APP_TOKEN = app_token;
		SECRET = secret;
		EMAIL = null;
		AUTH_TOKEN = null;
		USER_SLUG = null;
		datasource = null;
		init();
	}
	
	/**
	 * This constructor will be used when the OOS4Java is going to be used in authenticated mode, hence
	 * having access to user's personal agenda.
	 * 
	 * The constructor needs the application token and secret, which must have been got from 11870.com.
	 * See http://groups.google.com/group/api-11870/web/api-keys for further information.
	 * 
	 * Also this constructor needs the user email and slug and the authToken. All three collected during
	 * user authorization of the application.
	 */
	public OOS4Java(String email, String authToken, String userSlug) throws OOSException
	{
		if (email==null || authToken==null || email.length()==0 || authToken.length()==0)
		{
			throw new OOSException("To get an authenticated service instance you must pass valid email and authToken. Perhaps you wanted to use the other constructor: OOS4Java(String app_token, String secret)"); 
		}
		APP_TOKEN = null;
		SECRET = null;
		EMAIL = email;
		AUTH_TOKEN = authToken;
		USER_SLUG = userSlug;
		init();
		datasource = new APPDataSource(EMAIL, AUTH_TOKEN, log);
	}
	
	public UserServiceDao getUserServiceDAO()
	{
		return new UserServiceDao(datasource);
	}

	/**
	 * Builds a search url, if no location is available, set any of the three location parameters to -1.
	 * @param query search query
	 * @param lat WGS84 latitude
	 * @param lon WGS84 longitude
	 * @param radius radius in kilometers
	 * @return search url
	 * @throws MalformedURLException
	 */
	public URL buildSearchURL(String query, Double lat, Double lon, Double radius) throws MalformedURLException
	{
		StringBuilder url = new StringBuilder();
		String auth = DigestUtils.md5Hex(APP_TOKEN + SECRET);
		url.append(API_BASE_URL).append("/search?q=").append(query).
			append("&authSign=").append(auth).append("&appToken=").append(APP_TOKEN);
		
		if (lat!=null && lon!=null && radius!=null) {
			url.append("&lat=").append(lat).append("&lon=").append(lon).append("&radius=").append(radius);
		}
		
		log.v(LOG_TAG, "query: "+url);
		return new URL(url.toString());
	}
	
	public URL buildUserSitesURL() throws MalformedURLException
	{
		StringBuilder url = new StringBuilder();
		url.append(API_BASE_URL).append("/sites/").append(USER_SLUG);
		return new URL(url.toString());
	}

	/**
	 * Retrieves a service document. Requires OOS4Java to have an authToken
	 * and the email corresponding to that user.
	 * @param email
	 * @param authToken
	 * @return Service document
	 * @throws OOSException
	 */
	public Document getServiceDocument(String email, String authToken) throws OOSException
	{
		try
		{
			return urlUtils.retrieveWSSE(new URL(API_BASE_URL), email, authToken);
		}
		catch (Exception e)
		{
			throw new OOSException("could not get service document, perhaps application is not authenticated", e); 
		}
	}
	
	/**
	 * Performs a search against 11870 api. Use this method when position is not 
	 * important/available.
	 * @param query
	 * @return
	 */
	public ArrayList<SearchService> search(String query)
	{
		return search(query, null, null, null);
	}
	
	/**
	 * Perform a search against 11870 API.
	 * @param query
	 * @param lat
	 * @param lon
	 * @param radius
	 * @return
	 */
	public ArrayList<SearchService> search(String query, Double lat, Double lon, Double radius)
	{
		ArrayList<SearchService> services = new ArrayList<SearchService>();
		SearchServiceDao dao = new SearchServiceDao();
		try
		{
			Document doc = urlUtils.retrieve(buildSearchURL(query, lat, lon, radius));

			Element root = doc.getDocumentElement();
			NodeList entries = root.getElementsByTagName("entry");

			log.v(LOG_TAG, "query returned "+entries.getLength()+" services, parsing...");
			for (int i = 0; i < entries.getLength(); i++)
			{
				Element entry = (Element) entries.item(i);
				SearchService service = dao.getService(entry);
				services.add(service);
			}
		}
		catch (Exception e)
		{
			log.e(LOG_TAG, "can't get services from server", e);
		}
		return services;
	}

	/**
	 * Downloads services in user's agenda.
	 * @return ArrayList of UserService
	 * @throws OOSException Operation could not be completed
	 */
	public ArrayList<UserService> downloadUserAgenda() throws OOSException
	{
		if (!authenticated()) {
			throw new OOSException("user agenda can only be accessed from a service with authorization token, " +
				"must be used following constructor: public OOS4Java(app_token, secret, email, authToken, userSlug)");
		}
		
		ArrayList<UserService> services = new ArrayList<UserService>();
		UserServiceDao dao = new UserServiceDao();
		try
		{
			URL next = buildUserSitesURL();
			while (next!=null)
			{
				Document feed = null;
				for (int tries = 1; tries<=3; tries++) 
				{
					try
					{
						feed = urlUtils.retrieveWSSE(next, EMAIL, AUTH_TOKEN);
						break;
					}
					catch(SAXParseException saxe)
					{
						log.w(LOG_TAG, "Exception retrieving, attempt "+tries);
					}
				}
				Element root = feed.getDocumentElement();
				NodeList entries = root.getElementsByTagName("entry");
	
				log.v(LOG_TAG, "query returned "+entries.getLength()+" services, parsing...");
				for (int i = 0; i < entries.getLength(); i++)
				{
					Element entry = (Element) entries.item(i);
					UserService service = dao.getService(entry);
					services.add(service);
				}
				// get 'next' link, if there is any
				String nextString = atom.getElementValue(root, "link", "next");
				if (nextString !=null) next = new URL(nextString);
				else next = null;
			}
			log.v(LOG_TAG, services.size()+" services downloaded");
			return services;
		}
		catch (Exception e)
		{
			String message = "can't get services from server";
			//Log.e(LOG_TAG, message, e);
			throw new OOSException(message, e);
		}
	}
}
